//
//  ColorMaths.c
//  iOSStudy
//
//  Created by Evan Xie on 11/26/15.
//  Copyright © 2015 Family Wings. All rights reserved.
//

#include <math.h>
#include "ColorMaths.h"


static inline bool checkColorComponentValue(float component)
{
    if (component < 0.0 || component > 1.0) {
        return false;
    }
    
    return true;
}

bool RGBToHSV(RGBColor *inRGB, HSVColor *outHSV)
{
    if (inRGB == NULL || outHSV == NULL) {
        return false;
    }
    
    if (!checkColorComponentValue(inRGB->red) ||
        !checkColorComponentValue(inRGB->green) ||
        !checkColorComponentValue(inRGB->blue)) {
        
        return false;
    }
    
    float min = 0.0, max = 0.0, delta = 0.0;
    float hue = 0.0, satuation = 0.0, value = 0.0;
    
    min = fminf(inRGB->red, inRGB->green);
    min = fminf(min, inRGB->blue);
    max = fmaxf(inRGB->red, inRGB->green);
    max = fmaxf(max, inRGB->blue);
    
    delta = max - min;
    
    // Calculate hue
    if (delta == 0.0) {
        hue = -1.0;
        satuation = 0.0;
    } else {
        
        if (max == inRGB->red) {
            // Between yellow and magenta
            hue = (inRGB->green - inRGB->blue) / delta;
        } else if (max == inRGB->green) {
            // Between cyan and yellow
            hue = (inRGB->blue - inRGB->red) / delta + 2;
        } else {
            // Between magenta and cyan
            hue = (inRGB->red - inRGB->green) / delta + 4;
        }
    }
    
    hue *= 60; // Multiply 60 degrees
    if (hue < 0) {
        hue += 360;
    }
    hue /= 360.0;
    
    // Calculate satuation
    if (max == 0) {
        satuation = 0.0;
    } else {
        satuation = delta / max;
    }
    
    // Calcualte value
    value = max;
    
    outHSV->hue = hue;
    outHSV->satuation = satuation;
    outHSV->value = value;
    
    return true;
}

/**
 S = (Max(R,G,B)-Min(R,G,B)) / Max(R,G,B),
 V = Max(R,G,B)
 SV = V-Min(R,G,B)
 Min(R,G,B) = V-SV = V(1-S)
 */
bool HSVToRGB(HSVColor *inHSV, RGBColor *outRGB)
{
    if (inHSV == NULL || outRGB == NULL) {
        return false;
    }
    
    if (!checkColorComponentValue(inHSV->hue) ||
        !checkColorComponentValue(inHSV->satuation) ||
        !checkColorComponentValue(inHSV->value)) {
        
        return false;
    }
    
    if (inHSV->satuation == 0 ) {
        // Mix R, G, B colors in the same amount, we get a neutral color(achromatic color, grey)
        outRGB->red = outRGB->green = outRGB->blue = inHSV->value;
        return true;
    }
    
    float h = inHSV->hue * 360 / 60;
    float s = inHSV->satuation;
    float v = inHSV->value;
    
    /* 
     Hue sector, [0, 5]
     Red:   sectors 0 and 5
     Green: sectors 1 and 2
     Blue:  sectors 3 and 4
     */
    int hue_sector = floor(h);
    
    float f = h - hue_sector; // Factorial part of hue secotor
    float p = v * ( 1 - s );  // Also equal Min(R, G, B)
    float q = v * ( 1 - s * f ); // Middle value, only used in odd sectors
    float t = v * ( 1 - s * ( 1 - f ) ); // Middle value, only used in even sectors
    
    switch(hue_sector) {
        case 0:
            outRGB->red = v;
            outRGB->green = t;
            outRGB->blue = p;
            break;
        case 1:
            outRGB->red = q;
            outRGB->green = v;
            outRGB->blue = p;
            break;
        case 2:
            outRGB->red = p;
            outRGB->green = v;
            outRGB->blue = t;
            break;
        case 3:
            outRGB->red = p;
            outRGB->green = q;
            outRGB->blue = v;
            break;
        case 4:
            outRGB->red = t;
            outRGB->green = p;
            outRGB->blue = v;
            break;
        default:
            outRGB->red = v;
            outRGB->green = p;
            outRGB->blue = q;
            break;
    }
        
    return true;
}
